#include "cdock.h"
#include "cdockicon.h"
#include "claunchwindow.h"

#include <buola/image/cimage.h>
#include <buola/gui/cconfig.h>
#include <buola/gui/ctoolkit.h>
#include <buola/widgets/ctheme.h>
#include <buola/image/cgc.h>
#include <buola/gui/x11/cfixesregion.h>
#include <buola/gl/cgltexture.h>
#include <buola/gui/x11/cpixmap.h>
#include <buola/gui/x11/cx11display.h>
#include <buola/widgets/cmenubar.h>
#include <buola/gui/x11/cx11toolkit.h>
#include <buola/algorithm/container.h>
#include <X11/Xatom.h>

namespace buola { namespace bdesktop {

const double gMaxZoom=2.5;//3.0
const double gMaxAtten=2.0;//2.4
const double gOffset=10.0;
const double gSeparation=6.0;

double SmoothStep(double pX)
{
    return -2*pX*pX*pX+3*pX*pX;
}

double SmoothStep(double pMin,double pMax,double pX)
{
    if(pX<pMin) return 0;
    if(pX>pMax) return 1;
    double lF=((pX-pMin)/(pMax-pMin));

    return -2*lF*lF*lF+3*lF*lF;
}

double CalcIntegral(double pFar,double pNear)
{
    double lMin=(gMaxAtten-pFar)/gMaxAtten;
    double lMax=(gMaxAtten-pNear)/gMaxAtten;

    if(lMin<0)
        lMin=0;
    if(lMax>1)
        lMax=1;

    if(lMax<=lMin) return 0;

    double lR=((pow(lMax,3)-pow(lMax,4)/2)-(pow(lMin,3)-pow(lMin,4)/2));
    return lR;
}

double LinearStep(double pMin,double pMax,double pX)
{
    if(pX<pMin) return 0;
    if(pX>pMax) return 1;
    return (pX-pMin)/(pMax-pMin);
}

CDock::CDock()
    :   mMouseInside(false)
    ,   mMousePos(0,0)
    ,   mZoomStep(0.0)
    ,   mValid(VALID_NONE)
    ,   mAnimating(ANIMATE_NONE)
    ,   mTipIcon(-1)
    ,   mBackTexture(nullptr)
    ,   mFrozen(false)
{
    SetWMMode(WM_STICKY|WM_NODECORATION);
    SetPaintMode(PAINT_TRANSPARENT|PAINT_OPENGL);
}

void CDock::AddIcon(CDockIcon *pIcon,EGroup pGroup)
{
    int i;
    for(i=0;i<mIcons.size();i++)
    {
        if(mIcons[i]->mGroup>pGroup) break;
    }
    SIcon &lIcon=construct_insert(mIcons,mIcons.begin()+i);

    lIcon.mIcon=pIcon;
    lIcon.mGroup=pGroup;
    lIcon.mOpacityStep=0.0;

    StartAnimation(ANIMATE_OPACITYIN);

    Invalidate();
    Refresh();
}

void CDock::InsertIcon(CDockIcon *pIcon,EGroup pGroup,CDockIcon *pOther,
                                            EInsert pWhere)
{
    int i;
    for(i=0;i<mIcons.size();i++)
    {
        if(pOther==mIcons[i]->mIcon) break;
    }

    if(i>=mIcons.size())
    {
        AddIcon(pIcon,pGroup);
        return;
    }

    if(pWhere==INSERT_AFTER) i++;

    SIcon &lIcon=construct_insert(mIcons,mIcons.begin()+i);

    lIcon.mIcon=pIcon;
    lIcon.mGroup=pGroup;
    lIcon.mOpacityStep=0.0;

    StartAnimation(ANIMATE_OPACITYIN);

    Invalidate();
    Refresh();
}

void CDock::RemoveIcon(CDockIcon *pIcon)
{
    for(int i=0;i<mIcons.size();i++)
    {
        if(mIcons[i]->mIcon==pIcon)
        {
            mIcons.erase(mIcons.begin()+i);
        }
    }

    Invalidate();
    Refresh();
}

void CDock::ShowMenu(gui::PMenu pMenu,const CPoint &pPos)
{
    gui::CMenuBar *lMenuBar=new gui::CMenuBar;
    lMenuBar->SetStyleBit(gui::CMenuBar::STYLE_BOTTOMPOS);
    lMenuBar->CreatePopup(this,pMenu,pPos);
    lMenuBar->eClosed.Connect(&CDock::OnMenuClosed,this);
    mFrozen=true;
    lMenuBar->Show();
}

double CDock::GetZoom(double pPos,double pL,double pR)
{
    if(pL==pR) return 1;
    double lInt;
    if(pPos<=pL)
    {
        lInt=CalcIntegral((pR-pPos)/mHeight,(pL-pPos)/mHeight);
    }
    else if(pPos>=pR)
    {
        lInt=CalcIntegral((pPos-pL)/mHeight,(pPos-pR)/mHeight);
    }
    else
    {
        lInt=CalcIntegral((pPos-pL)/mHeight,0)+
                CalcIntegral((pR-pPos)/mHeight,0);
    }

    return 1+lInt*gMaxAtten*(gMaxZoom-1)*mHeight/(pR-pL);
}

void CDock::StartAnimation(EAnimation pType)
{
    if(!mAnimating)
    {
        mTimer->Enable();
    }

    mAnimating|=pType;
}

void CDock::StopAnimation(EAnimation pType)
{
    mAnimating&=~pType;

    if(!mAnimating)
    {
//      mTimer->Disable();
    }
}

void CDock::RecalcRestRect()
{
    double lW=0.0;
    for(int i=0;i<mIcons.size();i++)
    {
        lW+=mIcons[i]->mIcon->GetRelativeWidth();
    }

    double lTW=lW*mHeight+gSeparation*(mIcons.size()-1);

    CSize lRootSize=gui::get_toolkit()->GetRootWindow()->GetSize();
    double lX=lRootSize.x/2-lTW/2;

    mIconsRect.l=lX;
    mIconsRect.t=mRect.b-mHeight-gOffset;
    mIconsRect.b=mRect.b-gOffset;

    for(int i=0;i<mIcons.size();i++)
    {
        double lW=mHeight*mIcons[i]->mIcon->GetRelativeWidth();
        mIcons[i]->mRestRect.Set(CPoint(lX,mRect.b-mHeight-gOffset),CSize(lW,mHeight));
        mIcons[i]->mZoomRect=mIcons[i]->mRestRect;
//      mIcons[i]->mIcon->SetPos(mIcons[i]->mRestRect);
        lX+=lW;
        lX+=gSeparation;
    }

    mIconsRect.r=lX;

    mValid|=VALID_RESTRECT;
    
    SetShape(mIconsRect,gui::SHAPE_INPUT);
}

void CDock::RecalcZoomRect()
{
    if(!mIcons.size()) return;
    int i;

    for(i=0;i<mIcons.size()-1;i++)
    {
        if(mMousePos.x<mIcons[i]->mRestRect.r) break;
    }
    mTipIcon=i;

    double lMainPosX;

    if(mMousePos.x<mIcons[i]->mRestRect.l)   //we are in a separation
    {
        double lSepZoom=GetZoom(mMousePos.x,mIcons[i]->mRestRect.l-gSeparation,
                                            mIcons[i]->mRestRect.l);
        double lLeftZoom=GetZoom(mMousePos.x,mIcons[i]->mRestRect.l-gSeparation,
                                            mMousePos.x);
        double lLeftSize=lLeftZoom*(mMousePos.x-(mIcons[i]->mRestRect.l-gSeparation));
        lMainPosX=mMousePos.x-lLeftSize+lSepZoom*gSeparation;
    }
    else
    {
        double lLeftZoom=GetZoom(mMousePos.x,mIcons[i]->mRestRect.l,mMousePos.x);
        double lLeftSize=(mMousePos.x-mIcons[i]->mRestRect.l)*lLeftZoom;
        lMainPosX=mMousePos.x-lLeftSize;
    }

    double lMainZoom=GetZoom(mMousePos.x,mIcons[i]->mRestRect.l,mIcons[i]->mRestRect.r);
    CSize lMainSize=mIcons[i]->mRestRect.Size()*lMainZoom;
    mIcons[i]->mZoomRect.Set(CPoint(lMainPosX,mRect.b-lMainSize.y-gOffset),lMainSize);

    double lX=mIcons[i]->mZoomRect.l;

    for(int j=i-1;j>=0;j--)
    {
        double lZoom=GetZoom(mMousePos.x,mIcons[j]->mRestRect.l,mIcons[j]->mRestRect.r);
        CSize lSize=mIcons[j]->mRestRect.Size()*lZoom;
        lX-=gSeparation*GetZoom(mMousePos.x,mIcons[j]->mRestRect.r,
                                mIcons[j]->mRestRect.r+gSeparation);
        lX-=mIcons[j]->mRestRect.w()*lZoom;
        mIcons[j]->mZoomRect.Set(CPoint(lX,mRect.b-lSize.y-gOffset),lSize);
    }

    lX=mIcons[i]->mZoomRect.r;

    for(int j=i+1;j<mIcons.size();j++)
    {
        lX+=gSeparation*GetZoom(mMousePos.x,mIcons[j]->mRestRect.l-gSeparation,
                                mIcons[j]->mRestRect.l);
        double lZoom=GetZoom(mMousePos.x,mIcons[j]->mRestRect.l,mIcons[j]->mRestRect.r);
        CSize lSize=mIcons[j]->mRestRect.Size()*lZoom;
        mIcons[j]->mZoomRect.Set(CPoint(lX,mRect.b-lSize.y-gOffset),lSize);
        lX+=mIcons[j]->mRestRect.w()*lZoom;
    }

    mValid|=VALID_ZOOMRECT;
}

void CDock::RecalcEffRect()
{
    for(int i=0;i<mIcons.size();i++)
    {
        mIcons[i]->mEffRect=mIcons[i]->mRestRect+
                    (mIcons[i]->mZoomRect-mIcons[i]->mRestRect)*
                                SmoothStep(mZoomStep);
        mIcons[i]->mIcon->SetPos(mIcons[i]->mEffRect);//TODO:remove this
    }

    mValid|=VALID_EFFRECT;
}

//!\todo:implement the tips in a totally different way, as it is really slow this way

void CDock::ShowTip(const CPoint &pPos,const std::wstring &pText)
{
    return;
    if(!mDockTip)
    {
        if(!pText.length())
        {
            return;
        }

        mDockTip=new CDockTip;
        mDockTip->Create(nullptr);
    }

    if(pText!=mDockTip->GetCaption())
    {
        mDockTip->SetCaption(pText);
    }

    if(!pText.length())
    {
        mDockTip->Hide();
    }
    else
    {
        mDockTip->SetPos(pPos);
        mDockTip->Show();
        mDockTip->BringToTop();
    }
}

void CDock::HideTip()
{
    if(mDockTip)
    {
        mDockTip->Hide();
    }
}

CGLTexture *CDock::GetBackTexture()
{
    double lIndent=8.0;
    if(!mBackTexture)
    {
        mBackPixmap=new gui::x11::CPixmap;
        mBackPixmap->Create(CIntSize(100,50),32);

        CGC *lGC=mBackPixmap->GetGC(GC_VECTOR);
        lGC->SetOperator(CGC::OP_SRC);
        lGC->SetSourceColor(CColor(0.0,0.0,0.0,0.0));
        lGC->Rectangle(CPoint(0,0),CSize(100,50));
        lGC->FillPath();
        lGC->SetSourceColor(CColor(0.60,0.60,0.60,0.70));
        lGC->MoveTo(CPoint(0,48));
        lGC->LineTo(CPoint(lIndent,24));
        lGC->LineTo(CPoint(100-lIndent,24));
        lGC->LineTo(CPoint(100,48));
        lGC->ClosePath();
        lGC->FillPath();
        lGC->SetSourceColor(CColor(0.70,0.70,0.70,0.70));
        lGC->MoveTo(CPoint(lIndent,24));
        lGC->LineTo(CPoint(2*lIndent,0));
        lGC->LineTo(CPoint(100-2*lIndent,0));
        lGC->LineTo(CPoint(100-lIndent,24));
        lGC->ClosePath();
        lGC->FillPath();
        lGC->SetSourceColor(CColor(0.8,0.8,0.8,0.9));
        lGC->Rectangle(CPoint(0,48),CSize(100,2));
        lGC->FillPath();

        delete lGC;
        XSync(gui::x11::get_default_x11_display()->GetDisplay(),false);

        mBackTexture=new CGLTexture;
        mBackTexture->Bind(mBackPixmap);
    }

    return mBackTexture;
}

void CDock::OnCreate()
{
    mTimer=new CTimer(std::chrono::milliseconds(15));
    mTimer->sTimer.Connect(&CDock::OnAnimationTimer,this);

    SetBackNone();

    mHeight=48;

    CSize lSize=gui::get_toolkit()->GetRootWindow()->GetSize();

    SetWindowClass(CLASS_DOCK);

    mAltF2.Set(KEY_F2,KEYMOD_ALT,gui::CAccelerator::GLOBAL,this);

    SetStrut(CRect(0,0,0,mHeight));
    MoveResize(CPoint(0,lSize.y-gMaxZoom*mHeight-gOffset),
                    CSize(lSize.x,gMaxZoom*mHeight+gOffset));

    TID lID=GetID();
    gui::x11::get_default_x11_display()->SetWindowProperty(lID,gui::x11::get_default_x11_display()->Atoms().mBuolaDockWindow,XA_WINDOW,32,1,&lID);
}

void CDock::OnButtonDown(gui::CMouseEvent *pE)
{
    if(mMouseInside&&mTipIcon>=0&&mTipIcon<mIcons.size())
    {
        mIcons[mTipIcon]->mIcon->OnClick(pE);
    }
}

void CDock::OnMouseMove(gui::CMouseEvent *pE)
{
    if(mFrozen) return;
    mMousePos=pE->mPoint;
    mRootMousePos=pE->mRootPoint;
    bool lInsideIcons=mIconsRect.Contains(pE->mPoint);

    if(!lInsideIcons)
    {
        if(mMouseInside)
        {
            mMouseInside=false;
            StartAnimation(ANIMATE_ZOOMOUT);
            StopAnimation(ANIMATE_ZOOMIN);
        }
        return;
    }

    if(!mMouseInside)
    {
        mMouseInside=true;
        StartAnimation(ANIMATE_ZOOMIN);
        StopAnimation(ANIMATE_ZOOMOUT);
    }

    Invalidate(VALID_ZOOMRECT|VALID_EFFRECT);
    Refresh();
}

void CDock::OnMouseOut(gui::CMouseEvent*)
{
    if(mFrozen) return;
    if(mMouseInside)
    {
        mMouseInside=false;
        StartAnimation(ANIMATE_ZOOMOUT);
        StopAnimation(ANIMATE_ZOOMIN);
    }
}

void CDock::OnPreExpose()
{
    if(!(mValid&VALID_RESTRECT))
    {
        RecalcRestRect();
    }
    if(!(mValid&VALID_ZOOMRECT))
    {
        RecalcZoomRect();
    }
    if(!(mValid&VALID_EFFRECT))
    {
        RecalcEffRect();
    }

    if(mMouseInside&&mTipIcon>=0&&mTipIcon<mIcons.size())
    {
        ShowTip(CPoint(mIcons[mTipIcon]->mEffRect.GetCenter().x,
                    mIcons[mTipIcon]->mEffRect.t+GetRootPos().y),
                            mIcons[mTipIcon]->mIcon->mText);
//      ShowTip(CPoint(mIcons[mTipIcon]->mEffRect.GetCenter().x,GetRootPos().y),
//                          mIcons[mTipIcon]->mIcon->mText);
//      ShowTip(CPoint(mRootMousePos.x,GetRootPos().y),
//                          mIcons[mTipIcon]->mIcon->mText);
    }
    else
    {
        HideTip();
    }
}

void CDock::OnExpose(gui::CExposeEvent *pE)
{
    for(int i=0;i<mIcons.size();i++)
    {
        CRect lR=mIcons[i]->mEffRect;
        mIcons[i]->mIcon->Draw(CRect(lR.l,lR.b-2+lR.h(),lR.r,lR.b-2),
                    mIcons[i]->mOpacityStep);
    }
    if(mIcons.size())
    {
        GetBackTexture()->Enable(false);
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);

        CRect lR(mIcons[0]->mEffRect.l,mRect.b-mHeight/2-5,
                mIcons[mIcons.size()-1]->mEffRect.r,mRect.b);

        double lTexArray[]={0.0,1.0, 0.0,0.0, 0.35,1.0, 0.35,0.0,
                            0.65,1.0, 0.65,0.0, 1.0,1.0, 1.0,0.0};
        double lVertexArray[]={lR.l-30,lR.b, lR.l-30,lR.t, lR.l-5,lR.b, lR.l-5,lR.t,
                               lR.r+5,lR.b, lR.r+5,lR.t, lR.r+30,lR.b, lR.r+30,lR.t};

        glColor4f(1.0,1.0,1.0,1.0);
        glVertexPointer(2,GL_DOUBLE,0,lVertexArray);
        glTexCoordPointer(2,GL_DOUBLE,0,lTexArray);
        glEnable(GL_BLEND);
        glDrawArrays(GL_QUAD_STRIP,0,8);
        GetBackTexture()->Disable();
    }

    for(int i=0;i<mIcons.size();i++)
    {
        CRect lR=mIcons[i]->mEffRect;
        mIcons[i]->mIcon->Draw(CRect(lR.l,lR.t,lR.r,lR.b),mIcons[i]->mOpacityStep);
    }
}

void CDock::OnSize(gui::CGeometryEvent*)
{
    Invalidate();
}

void CDock::OnKeyDown(gui::CKeyEvent *pE)
{
    if(pE->mMods==KEYMOD_ALT)
    {
        if(pE->mKey==KEY_F2)
        {
            CLaunchWindow *lWindow=new CLaunchWindow;
            lWindow->Create(nullptr);
            lWindow->SetAutoDelete(true);
            lWindow->Show();
        }
    }
}

void CDock::OnAnimationTimer()
{
    if(mAnimating&ANIMATE_ZOOMIN)
    {
        mZoomStep+=0.08;
        if(mZoomStep>=1.0)
        {
            mZoomStep=1.0;
            StopAnimation(ANIMATE_ZOOMIN);
        }
        Invalidate(VALID_EFFRECT);
        Refresh();
    }
    else if(mAnimating&ANIMATE_ZOOMOUT)
    {
        mZoomStep-=0.08;
        if(mZoomStep<=0.0)
        {
            mZoomStep=0.0;
            StopAnimation(ANIMATE_ZOOMOUT);
        }
        Invalidate(VALID_EFFRECT);
        Refresh();
    }
    else if(mAnimating&ANIMATE_OPACITYIN)
    {
        bool lSomeLeft=false;
        for(int i=0;i<mIcons.size();i++)
        {
            if(mIcons[i]->mOpacityStep>=1.0) continue;

            mIcons[i]->mOpacityStep+=0.05;
            if(mIcons[i]->mOpacityStep>=1.0)
            {
                mIcons[i]->mOpacityStep=1.0;
                continue;
            }
            lSomeLeft=true;
        }

        if(!lSomeLeft)
            StopAnimation(ANIMATE_OPACITYIN);
        Refresh();
    }
}

void CDock::OnMenuClosed()
{
    mFrozen=false;
    OnMouseOut(nullptr);
}

/*namespace bdesktop*/ } /*namespace buola*/ }
